import { highlight } from './../utils/highlight';
import path from 'path';
import fs from 'fs';
import MarkdownIt from 'markdown-it';
import mdContainer from 'markdown-it-container';
import { render } from '../utils/render';
import type Token from 'markdown-it/lib/token';
import type Renderer from 'markdown-it/lib/renderer';

interface ContainerOpts {
    marker?: string | undefined;
    validate?(params: string): boolean;
    render?(tokens: Token[], index: number, options: any, env: any, self: Renderer): string;
}

const localMd = MarkdownIt();

export const farrisMarkdownPlugin = (md: MarkdownIt, options: any) => {
    md.use(mdContainer, 'demo', {
        validate(params: string) {
            return !!params.trim().match(/^demo\s*(.*)$/);
        },
        render(tokens: any[], index: number) {
            const isTagOpening = tokens[index].nesting === 1;
            const matchDescriptionResult = tokens[index].info.trim().match(/^demo\s*(.*)$/);
            const hasDescription = matchDescriptionResult && matchDescriptionResult.length > 1;
            if (isTagOpening) {
                let sourceCode: string = tokens[index + 1].type === 'fence' ? tokens[index + 1].content : '';
                const startTag = '{';
                const endTag = '}';
                const hasVuePath = sourceCode.trim().indexOf(startTag) > -1 && sourceCode.indexOf(endTag) > -1;
                if (hasVuePath) {
                    const start = sourceCode.indexOf(startTag) + startTag.length;
                    const end = sourceCode.indexOf(endTag, start);
                    const vueFilePath = sourceCode.slice(start, end);
                    sourceCode = fs.readFileSync(path.resolve('', vueFilePath), 'utf-8');
                    tokens[index + 1].content = sourceCode;
                }
                return `<demo source-code="${encodeURIComponent(sourceCode)}">${sourceCode ? `<!--vue-demo:${sourceCode}:vue-demo-->` : ''}`;
            } else {
                return '</demo>';
            }
        }
    });
    const lang = options?.lang || 'vue';
    const defaultRender = md.renderer.rules.fence;
    md.renderer.rules.fence = (tokens: any[], index: number, option: any, env: any, self: any) => {
        const token = tokens[index];
        const prevToken = tokens[index - 1];
        const isFenceInMDContainer = !!(prevToken && prevToken.nesting === 1 && prevToken.info.trim().match(/^demo\s*(.*)$/));
        const shouldOverrideRender = token.info.trim() === lang && isFenceInMDContainer;
        if (shouldOverrideRender) {
            const matchDescriptionResult = prevToken.info.trim().match(/^demo\s*(.*)$/);
            const hasDescription = matchDescriptionResult && matchDescriptionResult.length > 1;
            const description = hasDescription ? matchDescriptionResult[1] : '';
            return `
                ${
                    description
                        ? `<template #description>
                        <div>${md.renderInline(description)}</div>
                    </template>`
                        : ''
                }
                <template #highlight>
                    <div class="language-${lang}">${option.highlight(token.content, lang, '')}</div>
                </template>`;
        }
        return defaultRender ? defaultRender(tokens, index, options, env, self) : '';
    };
    const originalRender = md.render;
    md.render = (src: string, env?: any) => {
        let result = originalRender.call(md, src, env);
        const startTag = '<!--vue-demo:';
        const endTag = ':vue-demo-->';
        const hasVueDemo = result.indexOf(startTag) > -1 && result.indexOf(endTag) > -1;
        if (hasVueDemo) {
            const { template, script, style } = render(result, options);
            result = template;
            const data = md['__data'];
            const hoistedTags = data.hoistedTags || (data.hoistedTags = []);
            hoistedTags.push(script);
            hoistedTags.push(style);
        }
        return result;
    };
    md.use(mdContainer, 'vdemo', {
        validate(params) {
            return !!params.trim().match(/^vdemo\s*(.*)$/);
        },

        render(tokens: any[], idx: number, option: any) {
            const m = tokens[idx].info.trim().match(/^vdemo\s*(.*)$/);
            if (tokens[idx].nesting === 1 /* means the tag is opening */) {
                const description = m && m.length > 1 ? m[1] : '';
                const sourceFileToken = tokens[idx + 2];
                let source = '';
                let highlightSourceCode = '';
                // const sourceFile = sourceFileToken.children?.[0].content ?? '';
                const sourceFile: string = tokens[idx + 1].type === 'fence' ? tokens[idx + 1].content : '';
                const startTag = '{';
                const endTag = '}';
                const hasVuePath = sourceFile.trim().indexOf(startTag) > -1 && sourceFile.indexOf(endTag) > -1;
                if (hasVuePath) {
                    const start = sourceFile.indexOf(startTag) + startTag.length;
                    const end = sourceFile.indexOf(endTag, start);
                    const vueFilePath = sourceFile.slice(start, end);
                    source = fs.readFileSync(path.resolve('', vueFilePath), 'utf-8');
                    highlightSourceCode = option.highlight(source, lang, '');
                }

                // if (sourceFileToken.type === 'inline') {
                //     source = fs.readFileSync(path.resolve(docRoot, 'examples', `${sourceFile}.vue`), 'utf-8');
                // }
                if (!source) throw new Error(`Incorrect source file: ${sourceFile}`);

                return `<VPDemo :demos="demos" source="${encodeURIComponent(
                    highlightSourceCode
                )}" path="${sourceFile}" source-code="${encodeURIComponent(source)}" description="${encodeURIComponent(
                    localMd.render(description)
                )}">`;
            } else {
                return '</VPDemo>';
            }
        }
    } as ContainerOpts);
};
